"""
答手 - 自动版 (终端版)
自动识别题目信息，使用终端显示
"""
import sys
import time
import re
import base64
import io
import json
import ctypes
import win32con
import win32gui
from datetime import datetime
import requests
from PIL import ImageGrab, Image
import pyautogui
from colorama import Fore, init

from config.settings import Config
from common.tools.init_service import InitService
from common.tools.api_client import APIClient
from common.tools.gpt_service import GPTService
from common.tools.ocr_service import OCRService
from common.tools.screenshot_util import do_screenshot
from common.tools.remove_symbol import remove_symbol
from common.tools.clear_console import clear_console
from common.tools.out import out
from common.tools.get_config import get_config


# 自动重置颜色
init(autoreset=True)

# 获取程序的窗口句柄并置顶
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)


class AutoAnswerApp:
    """自动答题应用"""
    
    def __init__(self):
        self.api_client = APIClient()
        self.gpt_service = GPTService()
        self.login_data = None
        self.coordinates = None
        self.bd_url = None
        
        # 选项字母到屏幕坐标的映射，例如 {"A": (x, y), "B": (x, y), ...}
        self.option_positions = {}
        
        # 本地 OCR API 地址
        self.ocr_url = "http://127.0.0.1:1224/api/ocr"
        
        # 当前题目信息
        self.current_question = 0
        self.total_questions = 0
        self.countdown = 0
        
        # 题目/倒计时轮次相关状态
        self.last_question_index = 0          # 上一次识别到的题号
        self.answer_requested = False         # 本题是否已经触发过题库/GPT请求
        self.answer_pending = False           # 本题是否正在等待题库/GPT返回
        
        # 当前识别的题目和选项文本
        self.current_question_text = ""
        self.current_option_text = ""
        
        # 监控状态
        self.monitoring = False
    
    def initialize(self):
        """初始化应用"""
        init_service = InitService()
        init_service.check_and_create_files()
        self.login_data = init_service.login()
        self._parse_coordinates()
        
        # 如果是付费用户，预先获取百度 token
        if self.login_data['data']['roles'] == '付费' and self.login_data['data'].get('bdApiKey'):
            from common.gpt import GPT_BD
            self.bd_url = GPT_BD.get_token(
                self.login_data['data']['bdApiKey'],
                self.login_data['data']['bdSecretKey']
            )
    
    def _parse_coordinates(self):
        """解析坐标配置"""
        try:
            coordinate_key = 'coordinate' + str(self.login_data['data']['coordinate'])
            coordinate_str = self.login_data['data'][coordinate_key]
            # 新坐标格式：x1,y1,x2,y2（信息）; x3,y3,x4,y4（题目）; x5,y5,x6,y6（选项）
            x1, y1, x2, y2, x3, y3, x4, y4, x5, y5, x6, y6 = map(int, coordinate_str.split(','))
            # 目前逻辑仍然只使用题目和选项，不使用信息坐标
            self.coordinates = {
                'info': (x1, y1, x2, y2),
                'question': (x3, y3, x4, y4),
                'option': (x5, y5, x6, y6),
                'full': (x3, y3, x6, y6)
            }
        except Exception as e:
            print('│ 当前参数方案，数值异常，请检查配置文件：' + str(e))
            out()
    
    def run(self):
        """运行应用主循环"""
        print('├───────────────────────────────────────')
        input('│ ' + Fore.GREEN + '按回车键启动自动识别...')
        
        self.monitoring = True
        self.start_monitoring()
    
    def start_monitoring(self):
        """开始监控"""
        print('┌───────────────────────────────────────')
        
        # 主监控循环
        try:
            while self.monitoring:
                # 识别信息区域
                info = self.recognize_info()
                
                if info:
                    # 更新当前题目信息
                    self.current_question = info['current']
                    self.total_questions = info['total']
                    self.countdown = info['countdown']
                    
                    # 显示当前状态
                    self.display_status()
                    
                    # 检查是否识别到题号
                    if self.current_question == 0:
                        print('\n│ 未识别到题号信息')
                    else:
                        # 检测是否进入新题：题号变化时重置与答案相关的状态
                        if self.current_question != self.last_question_index:
                            # 新题目出现，清空控制台
                            clear_console()
                            print('┌───────────────────────────────────────')
                            
                            self.last_question_index = self.current_question
                            
                            # 添加延迟，等待题目加载完成
                            print('│ 检测到新题目，等待题目画面加载...')
                            time.sleep(0.5)  # 等待0.5秒让题目完全加载
                            clear_console()
                            print('┌───────────────────────────────────────')
                            
                            # 识别到题号，执行answer_manual.py第94-130行的逻辑
                            self._recognize_question()
                            
                            # 显示当前题号和倒计时
                            self.display_status()
                
                # 短暂休眠
                # time.sleep(0.5)
                
        except KeyboardInterrupt:
            print('\n├───────────────────────────')
            print('│ ' + Fore.RED + '监控已停止')
            self.monitoring = False
    
    def display_status(self):
        """显示当前状态"""
        if self.total_questions > 0:
            status_text = f"当前题号: {self.current_question}/{self.total_questions}    倒计时: {self.countdown}s"
        else:
            status_text = f"当前题号: {self.current_question}    倒计时: {self.countdown}s"
        
        # 使用回车符回到行首，然后覆盖整行
        sys.stdout.write('\r│ ' + Fore.GREEN + status_text)
        sys.stdout.flush()
    
    def recognize_info(self):
        """识别信息区域"""
        try:
            x1, y1, x2, y2 = self.coordinates['info']
            
            # 截图指定区域
            screenshot = ImageGrab.grab(bbox=(x1, y1, x2, y2))
            
            # 使用OCR识别
            ocr_text = self.ocr_recognize(screenshot)
            
            # 解析题目信息
            info = self.parse_question_info(ocr_text)
            
            return info
            
        except Exception as e:
            print(f'\n│ 识别信息区域失败: {str(e)}')
            return None
    
    def parse_question_info(self, text):
        """解析题目信息文本"""
        info = {
            'current': 0,
            'total': 0,
            'countdown': 0
        }
        
        # 匹配 "问题6/15" 格式
        question_match = re.search(r'问题(\d+)/(\d+)', text)
        if question_match:
            info['current'] = int(question_match.group(1))
            info['total'] = int(question_match.group(2))
        
        # 匹配倒计时格式
        countdown_match = re.search(r'(?:倒计时[：:]\s*)?(\d+)s', text)
        if countdown_match:
            info['countdown'] = int(countdown_match.group(1))
        
        return info
    
    def ocr_recognize(self, image):
        """使用本地OCR API识别图像"""
        try:
            # 转换图像为base64
            img_base64 = self.image_to_base64(image)
            
            # 构建请求数据
            data = {
                "base64": img_base64,
                "options": {
                    "data.format": "text",
                }
            }
            
            # 设置请求头
            headers = {"Content-Type": "application/json"}
            data_str = json.dumps(data)
            
            # 发送POST请求
            response = requests.post(self.ocr_url, data=data_str, headers=headers)
            response.raise_for_status()
            
            # 解析响应
            res_dict = json.loads(response.text)
            
            if res_dict.get("code") == 100:
                # 直接获取识别的文本字符串
                ocr_data = res_dict.get("data", "")
                if isinstance(ocr_data, str):
                    # 将换行符替换为空格,便于解析
                    return ocr_data.replace('\n', ' ')
                else:
                    return ""
            else:
                return ""
                
        except Exception as e:
            return ""
    
    def image_to_base64(self, image):
        """将PIL图像转换为base64字符串"""
        buffer = io.BytesIO()
        
        # 确保图像是RGB模式
        if image.mode != 'RGB':
            image = image.convert('RGB')
            
        # 保存为PNG格式
        image.save(buffer, format='PNG')
        img_str = base64.b64encode(buffer.getvalue()).decode()
        return img_str
    
    def _recognize_question(self):
        """识别题目 - 先识别题目并查询题库，如果题库没有答案再识别选项"""
        # 先只识别题目
        if not self.recognize_question_only():
            print('│ ' + Fore.RED + '题目识别失败')
            return False
        
        # 查询题库
        question = self.current_question_text
        if self.api_client.get_question_answer(remove_symbol(question)):
            return False
        
        print('├───────────────────────────')
        print('│ ' + Fore.YELLOW + '题库未查询到！')
        
        # 检查权限
        if not self._check_ai_permission():
            return False
        
        # 识别选项并查询 GPT
        return self._query_gpt(question)
    
    def _check_ai_permission(self):
        """检查 AI 功能权限"""
        if self.login_data['data']['roles'] == '白嫖':
            print('│ ———————————')
            print('│ ' + Fore.RED + '您尚未开通AI功能！')
            print('├───────────────────────────')
            return False
        
        if self.login_data['data']['weeks'] == 0:
            print('│ ———————————')
            print('│ ' + Fore.RED + '您已到期!AI功能已禁用！')
            print('├───────────────────────────')
            return False
        
        return True
    
    def _query_gpt(self, question):
        """识别选项并查询 GPT"""
        print('├───────────────────────────')
        
        # 识别选项
        if not self.recognize_options_if_needed():
            print('│ ' + Fore.RED + '选项识别失败')
            return False
        
        # 组合问题
        option = self.current_option_text
        full_question = '问题：' + question + ' ' + option
        print('│ ' + full_question)
        print('│ ———————————')
        
        # 并发查询所有 GPT 服务
        self.gpt_service.query_all(self.login_data, full_question)
        
        print('├───────────────────────────')
        print('│ ' + Fore.GREEN + 'GPT查询完成！')
        print('├───────────────────────────')
        
        # 保存完整截图
        x1, y1, x4, y4 = self.coordinates['full']
        timestamp = datetime.now().strftime('%m-%d %H-%M-%S')
        do_screenshot(x1, y1, x4, y4, timestamp)
        
        return True

    def recognize_question_only(self):
        """只识别题目，不识别选项"""
        
        # 识别题目
        try:
            x1, y1, x2, y2 = self.coordinates['question']
            
            # 保存题目截图
            if do_screenshot(x1, y1, x2, y2, 'question'):
                # 使用腾讯OCR识别
                ocr_text = OCRService.recognize(
                    self.login_data['data']['txOcrSecretId'],
                    self.login_data['data']['txOcrSecretKey'],
                    self.login_data['data']['txAddress'],
                    f'{Config.SCREENSHOT_DIR}/question.png',
                    'question'
                )
                if ocr_text:
                    self.current_question_text = ocr_text
                    print('│ 识别题目：' + ocr_text)
                    return True
        except Exception as e:
            print(f'│ 识别题目失败: {str(e)}')
        
        return False
    
    def recognize_options_if_needed(self):
        """只在需要时识别选项"""
        
        # 识别选项
        try:
            x1, y1, x2, y2 = self.coordinates['option']
            
            # 保存选项截图
            if do_screenshot(x1, y1, x2, y2, 'option'):
                # 使用腾讯OCR识别
                ocr_text = OCRService.recognize(
                    self.login_data['data']['txOcrSecretId'],
                    self.login_data['data']['txOcrSecretKey'],
                    self.login_data['data']['txAddress'],
                    f'{Config.SCREENSHOT_DIR}/option.png',
                    'option'
                )
                if ocr_text:
                    self.current_option_text = ocr_text
                    
                    # 解析选项文本并计算选项位置
                    self._parse_option_positions(ocr_text)
                    
                    # 解析选项并保存到self.options供后续使用
                    lines = [l.strip() for l in ocr_text.splitlines() if l.strip()]
                    self.options = []
                    letters_default = ["A", "B", "C", "D"]
                    
                    # 检查是否是腾讯OCR返回的格式：选项：[选项1 选项2 选项3 选项4]
                    tx_ocr_pattern = re.match(r'选项[:：]\s*\[(.*?)\]', ocr_text)
                    if tx_ocr_pattern:
                        # 处理腾讯OCR格式：提取方括号内的选项文本
                        options_text_content = tx_ocr_pattern.group(1)
                        # 尝试分割选项，可能的分隔符包括空格、制表符等
                        option_parts = re.split(r'\s+', options_text_content.strip())
                        
                        # 过滤掉可能的数字或编号（如"0014"）
                        filtered_parts = []
                        for part in option_parts:
                            # 如果不是纯数字，则保留
                            if not re.match(r'^\d+$', part):
                                filtered_parts.append(part)
                        
                        # 为每个选项分配字母
                        for i, text in enumerate(filtered_parts[:4]):  # 最多取4个选项
                            if i < len(letters_default):
                                letter = letters_default[i]
                                self.options.append({"letter": letter, "text": text})
                    else:
                        # 原有的解析逻辑，用于处理其他格式
                        for idx, line in enumerate(lines):
                            # 先尝试匹配以 "A." / "A:" / "A、" / "A " 开头的形式
                            m = re.match(r"([A-Da-d])[.:、]\s*(.*)", line)
                            if not m:
                                m = re.match(r"([A-Da-d])\s+(.*)", line)
                            if m:
                                letter = m.group(1).upper()
                                text = m.group(2).strip()
                            else:
                                # 没有显式字母前缀时，按顺序默认 A/B/C/D
                                letter = letters_default[idx] if idx < len(letters_default) else chr(ord('A') + idx)
                                text = line
                            self.options.append({"letter": letter, "text": text})
                    return True
        except Exception as e:
            print(f'│ 识别选项失败: {str(e)}')
        
        return False

    
    
    def detect_has_colored_option(self):
        """检测选项截图中是否存在接近红/绿色的像素，用于判断是否为第二次倒计时
        
        如果选项中已经出现红/绿高亮，认为是第二次倒计时
        返回True表示是第二次倒计时，False表示是第一次倒计时
        """
        try:
            x1, y1, x2, y2 = self.coordinates['option']
            opt_img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
            
            if opt_img.mode != 'RGB':
                img = opt_img.convert('RGB')
            else:
                img = opt_img

            width, height = img.size
            step_x = max(1, width // 80)
            step_y = max(1, height // 40)

            for y in range(0, height, step_y):
                for x in range(0, width, step_x):
                    r, g, b = img.getpixel((x, y))
                    # 粗略判断红色：R 高、G/B 低
                    if r > 180 and g < 120 and b < 120:
                        return True
                    # 粗略判断绿色：G 高、R/B 低
                    if g > 180 and r < 120 and b < 120:
                        return True
            return False
        except Exception:
            return False

    def _parse_option_positions(self, ocr_text):
        """解析选项文本并计算选项的大致位置
        
        根据选项区域的坐标计算ABCD选项的位置：
        - x坐标: (x6-x5)/2+x5 (选项区域的水平中心)
        - y坐标: A点是1*flag，B是3*flag，C是5*flag，D是7*flag
        - flag = (y6-y5)/8
        """
        try:
            # 初始化选项位置字典
            self.option_positions = {}
            
            # 获取选项区域的坐标
            x5, y5, x6, y6 = self.coordinates['option']
            
            # 计算flag值
            flag = (y6 - y5) / 8
            
            # 计算x坐标（选项区域的水平中心）
            center_x = (x6 - x5) / 2 + x5
            
            # 计算每个选项的y坐标
            # A点是1*flag，B是3*flag，C是5*flag，D是7*flag
            option_positions = {
                "A": (int(center_x), int(y5 + 1 * flag)),
                "B": (int(center_x), int(y5 + 3 * flag)),
                "C": (int(center_x), int(y5 + 5 * flag)),
                "D": (int(center_x), int(y5 + 7 * flag))
            }
            
            # 存储选项位置
            self.option_positions = option_positions
                
        except Exception as e:
            print(f"解析选项位置失败: {str(e)}")
            self.option_positions = {}

    def query_answer_async(self):
        """联动题库查询正确选项（优先题库，GPT兜底）。

        先获取题目文本查询题库，如果题库没有答案再识别选项并调用GPT。
        注意：此方法只在第一次倒计时期间调用。
        """
        print('├───────────────────────────')
        
        # 1. 获取当前 OCR 的题目文本
        question_text = self.current_question_text.strip()

        if not question_text:
            # 基础信息不足，直接结束
            self.handle_answer_result(None)
            return

        # 2. 先尝试题库查询
        try:
            resp = self.api_client.get_question_answer_detail(remove_symbol(question_text))
            # print(f"│ 题库查询结果: {resp}")
        except Exception:
            resp = None

        matched_letter = None
        answer_text = None
        
        # 3. 如果题库查询成功，尝试匹配答案
        if resp and resp.get("status") == 200:
            data = resp.get("data") or {}
            rows = data.get("rows") or []
            query_type = data.get("type", "")  # 获取查询类型：content为全文查询，words为关键词查询
            
            if rows:
                # 简单取第一条题库记录作为最相关
                answer_text = str(rows[0].get("answer", "")).strip()
                if answer_text:
                    # 输出题库查询信息
                    query_type_text = "全文查询" if query_type == "content" else "关键词查询"
                    print(f'│ 查询方式：{query_type_text}')
                    print('│ ————————————————')
                    print(f'│ 题库题目：{question_text}')
                    print(f'│ 题目答案：{answer_text}')
                    print(f'│ 上传作者：{self.login_data["data"]["username"]}')
                    print('├───────────────────────────')
                    print('│ 题库查询完成！')
                    print('├───────────────────────────')
        else:
            # 题库查询失败，输出未查询到信息
            print('│ 题库未查询到！')
            print('├───────────────────────────')
            print(f'│ 问题：{question_text}')
            print('│ ———————————')

        # 4. 如果题库有答案，需要识别选项来匹配答案
        if answer_text:
            # 调用选项识别方法
            if not self.recognize_options_if_needed():
                # 选项识别失败，无法匹配答案
                print('│ 选项识别失败，无法匹配题库答案')
                self.handle_answer_result(None)
                return
            
            # 获取选项文本
            option_text = self.current_option_text.strip()
            
            # 使用已解析的选项信息
            options = self.options
            if not options:
                # 如果没有解析出选项，尝试解析选项文本
                lines = [l.strip() for l in option_text.splitlines() if l.strip()]
                if not lines:
                    self.handle_answer_result(None)
                    return

                options = []  # [{"letter": "A", "text": "昆山市"}, ...]
                letters_default = ["A", "B", "C", "D"]
                
                # 检查是否是腾讯OCR返回的格式：选项：[选项1 选项2 选项3 选项4]
                tx_ocr_pattern = re.match(r'选项[:：]\s*\[(.*?)\]', option_text)
                if tx_ocr_pattern:
                    # 处理腾讯OCR格式：提取方括号内的选项文本
                    options_text = tx_ocr_pattern.group(1)
                    # 尝试分割选项，可能的分隔符包括空格、制表符等
                    option_parts = re.split(r'\s+', options_text.strip())
                    
                    # 过滤掉可能的数字或编号（如"0014"）
                    filtered_parts = []
                    for part in option_parts:
                        # 如果不是纯数字，则保留
                        if not re.match(r'^\d+$', part):
                            filtered_parts.append(part)
                    
                    # 为每个选项分配字母
                    for i, text in enumerate(filtered_parts[:4]):  # 最多取4个选项
                        if i < len(letters_default):
                            letter = letters_default[i]
                            options.append({"letter": letter, "text": text})
                else:
                    # 原有的解析逻辑，用于处理其他格式
                    for idx, line in enumerate(lines):
                        # 先尝试匹配以 "A." / "A:" / "A、" / "A " 开头的形式
                        m = re.match(r"([A-Da-d])[.:、]\s*(.*)", line)
                        if not m:
                            m = re.match(r"([A-Da-d])\s+(.*)", line)
                        if m:
                            letter = m.group(1).upper()
                            text = m.group(2).strip()
                        else:
                            # 没有显式字母前缀时，按顺序默认 A/B/C/D
                            letter = letters_default[idx] if idx < len(letters_default) else chr(ord('A') + idx)
                            text = line
                        options.append({"letter": letter, "text": text})
            
            # 用题库答案文本与 OCR 选项做模糊匹配，找到对应的选项字母
            answer_text_norm = answer_text.replace(" ", "").replace("，", ",")
            for opt in options:
                opt_text_norm = str(opt["text"]).strip().replace(" ", "")
                if not opt_text_norm:
                    continue
                # 题库答案包含选项文本，或选项文本包含题库答案
                if opt_text_norm in answer_text_norm or answer_text_norm in opt_text_norm:
                    matched_letter = opt["letter"].upper()
                    break

        # 5. 如果题库没有匹配到结果，则使用 GPT 获取答案
        if not matched_letter:
            # 检查权限
            if self.login_data['data']['roles'] == '白嫖':
                print('│ ' + Fore.RED + '您尚未开通AI功能！')
                print('├───────────────────────────')
                self.handle_answer_result(None)
                return
            
            if self.login_data['data']['weeks'] == 0:
                print('│ ' + Fore.RED + '您已到期!AI功能已禁用！')
                print('├───────────────────────────')
                self.handle_answer_result(None)
                return
            
            # 题库没有答案，需要识别选项并调用GPT
            # 调用选项识别方法
            if not self.recognize_options_if_needed():
                # 选项识别失败
                print('│ 选项识别失败，无法调用GPT')
                self.handle_answer_result(None)
                return
                
            # 获取选项文本
            option_text = self.current_option_text.strip()
            
            # 构建完整问题文本
            full_question = '问题：' + question_text + ' ' + option_text
            
            # 并发查询所有 GPT 服务
            self.gpt_service.query_all(self.login_data, full_question)
            
            print('├───────────────────────────')
            print('│ ' + Fore.GREEN + 'GPT查询完成！')
            print('├───────────────────────────')
            
            # 保存完整截图
            x1, y1, x4, y4 = self.coordinates['full']
            timestamp = datetime.now().strftime('%m-%d %H-%M-%S')
            do_screenshot(x1, y1, x4, y4, timestamp)
            
            # GPT查询完成，但没有匹配到具体答案
            self.handle_answer_result(None)
        else:
            # 题库查询成功，直接返回结果
            self.handle_answer_result({"letter": matched_letter, "text": answer_text})

    def handle_answer_result(self, result):
        """处理查询结果并移动鼠标到正确选项"""
        if result:
            # 找到匹配的选项文本
            matched_text = result.get("text", "")
            
            # 显示结果
            print('├───────────────────────────')
            print('│ ' + Fore.GREEN + f'答案：{result["letter"]}')
            print('├───────────────────────────')
            print(f'│ {result["letter"]}. {matched_text}')
            
            # 移动鼠标到正确选项
            self.move_mouse_to_option(result["letter"])
            
            # 保存完整截图
            x1, y1, x4, y4 = self.coordinates['full']
            timestamp = datetime.now().strftime('%m-%d %H-%M-%S')
            do_screenshot(x1, y1, x4, y4, timestamp)
        else:
            print('├───────────────────────────')
            print('│ ' + Fore.RED + '未找到答案')
            print('├───────────────────────────')
            
            # 保存完整截图
            x1, y1, x4, y4 = self.coordinates['full']
            timestamp = datetime.now().strftime('%m-%d %H-%M-%S')
            do_screenshot(x1, y1, x4, y4, timestamp)

    def move_mouse_to_option(self, letter):
        """根据选项字母将鼠标移动到对应选项中心点（不执行点击）
        
        注意：此方法在第一次倒计时期间被调用，确保在第二次倒计时开始前完成鼠标移动
        """
        if not letter:
            return
        letter = str(letter).strip().upper()
        pos = self.option_positions.get(letter)
        if not pos:
            return

        x, y = pos
        try:
            pyautogui.moveTo(x, y, duration=0.2)
            # 如需自动点击，可在此处取消注释：
            # pyautogui.click(x, y)
        except Exception:
            # 鼠标移动失败时不抛异常，避免影响主流程
            pass


def main():
    """主函数"""
    app = AutoAnswerApp()
    app.initialize()
    app.run()


if __name__ == '__main__':
    main()